home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 007 / tsr25src.arc / DISABLE.PAS next >
Pascal/Delphi Source File  |  1987-06-02  |  23KB  |  722 lines

  1. {**************************************************************************
  2. *   Activates or deactivates TSRs, while leaving them in memory.          *
  3. *   Copyright (c) 1987 Kim Kokkonen, TurboPower Software.                 *
  4. *   Released to the public domain for personal, non-commercial use only.  *
  5. ***************************************************************************
  6. *   version 2.3 5/4/87                                                    *
  7. *     first release. version number matches other TSR Utilities           *
  8. *   version 2.4 5/17/87                                                   *
  9. *     fix a bug during reactivate with more than one TSR deactivated      *
  10. *     turn off interrupts during disable and restore                      *
  11. *   version 2.5 6/2/87                                                    *
  12. *     make warning messages a little more useful                          *
  13. ***************************************************************************
  14. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  15. *   requires Turbo version 3 to compile.                                  *
  16. ***************************************************************************}
  17.  
  18. {$P128}
  19. {$C-}
  20.  
  21. program DisableTSR;
  22.   {-Deactivate and reactivate memory resident programs}
  23.   {-Leaving them in memory all the while}
  24. const
  25.   Version = '2.5';
  26.   MaxBlocks = 128;            {Max number of DOS allocation blocks supported}
  27.  
  28.   WatchID = 'TSR WATCHER';    {Marking string for WATCH}
  29.  
  30.   {Offsets into resident copy of WATCH.COM for data storage}
  31.   WatchOffset = $81;
  32.   NextChange = $104;
  33.   ChangeVectors = $220;
  34.   OrigVectors = $620;
  35.   CurrVectors = $A20;
  36.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  37.  
  38. type
  39.   {.F-}
  40.   Registers =
  41.   record
  42.     case Integer of
  43.       1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  44.       2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  45.   end;
  46.  
  47.   Block =
  48.   record                      {Store info about each memory block}
  49.     mcb : Integer;
  50.     psp : Integer;
  51.   end;
  52.  
  53.   BlockType = 0..MaxBlocks;
  54.   BlockArray = array[BlockType] of Block;
  55.  
  56.   ChangeBlock =
  57.   record                      {Store info about each vector takeover}
  58.     VecNum : byte;
  59.     case ID : byte of
  60.       0, 1 : (VecOfs, VecSeg : integer);
  61.       2    : (SaveCode : array[1..6] of byte);
  62.       $FF  : (PspAdd : integer);
  63.   end;
  64.   {
  65.   ID is interpreted as follows:
  66.     00 = ChangeBlock holds the new pointer for vector vecnum
  67.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  68.     02 = ChangeBlock holds the code underneath the vector patch
  69.     FF = ChangeBlock holds the segment of a new PSP
  70.   }
  71.   ChangeArray = array[0..maxchanges] of changeblock;
  72.  
  73.   HexString = string[4];
  74.   Pathname = string[64];
  75.   AllStrings = string[255];
  76.   {.F+}
  77.  
  78. var
  79.   Blocks : BlockArray;
  80.   WatchBlock, BlockNum : BlockType;
  81.   Regs : Registers;
  82.   Changes : ChangeArray;
  83.   ChangeMax, ActualMax, WatchSeg, PspHex, StartMCB : Integer;
  84.   Activate : Boolean;
  85.   TsrName : Pathname;
  86.  
  87.   procedure Abort(msg : AllStrings);
  88.     {-Halt in case of error}
  89.   begin
  90.     WriteLn(msg);
  91.     Halt(1);
  92.   end {Abort} ;
  93.  
  94.   function StUpcase(s : AllStrings) : AllStrings;
  95.     {-Return the uppercase string}
  96.   var
  97.     i : Byte;
  98.  
  99.   begin
  100.     for i := 1 to Length(s) do
  101.       s[i] := UpCase(s[i]);
  102.     StUpcase := s;
  103.   end {Stupcase} ;
  104.  
  105.   function Hex(i : Integer) : HexString;
  106.     {-Return hex representation of integer}
  107.   const
  108.     hc : array[0..15] of Char = '0123456789ABCDEF';
  109.   var
  110.     l, h : Byte;
  111.   begin
  112.     l := Lo(i);
  113.     h := Hi(i);
  114.     Hex := hc[h shr 4]+hc[h and $F]+hc[l shr 4]+hc[l and $F];
  115.   end {Hex} ;
  116.  
  117.   procedure FindTheBlocks;
  118.     {-Scan memory for the allocated memory blocks}
  119.   const
  120.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  121.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  122.   var
  123.     mcbSeg : Integer;         {Segment address of current MCB}
  124.     nextSeg : Integer;        {Computed segment address for the next MCB}
  125.     gotFirst : Boolean;       {True after first MCB is found}
  126.     gotLast : Boolean;        {True after last MCB is found}
  127.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  128.  
  129.     function GetStartMCB : Integer;
  130.       {-Return the first MCB segment}
  131.     begin
  132.       Regs.ah := $52;
  133.       MsDos(Regs);
  134.       GetStartMCB := MemW[Regs.es:(Regs.bx-2)];
  135.     end {Getstartmcb} ;
  136.  
  137.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  138.                             var gotFirst, gotLast : Boolean);
  139.       {-Store information regarding the memory block}
  140.     var
  141.       nextID : Byte;
  142.       PspAdd : Integer;       {Segment address of the current PSP}
  143.       mcbLen : Integer;       {Size of the current memory block in paragraphs}
  144.  
  145.     begin
  146.  
  147.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  148.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  149.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  150.       nextID := Mem[nextSeg:0];
  151.  
  152.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  153.         BlockNum := Succ(BlockNum);
  154.         gotFirst := True;
  155.         with Blocks[BlockNum] do begin
  156.           mcb := mcbSeg;
  157.           psp := PspAdd;
  158.         end;
  159.       end;
  160.  
  161.     end {Storetheblock} ;
  162.  
  163.   begin
  164.  
  165.     {Initialize}
  166.     StartMCB := GetStartMCB;
  167.     mcbSeg := StartMCB;
  168.     gotFirst := False;
  169.     gotLast := False;
  170.     BlockNum := 0;
  171.  
  172.     {Scan all memory until the last block is found}
  173.     repeat
  174.       idbyte := Mem[mcbSeg:0];
  175.       if idbyte = MidBlockID then begin
  176.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  177.         if gotFirst then
  178.           mcbSeg := nextSeg
  179.         else
  180.           mcbSeg := Succ(mcbSeg);
  181.       end else if gotFirst and (idbyte = EndBlockID) then begin
  182.         gotLast := True;
  183.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  184.       end else
  185.         {Start block was invalid}
  186.         Abort('Corrupted allocation chain or program error....');
  187.     until gotLast;
  188.  
  189.   end {Findtheblocks} ;
  190.  
  191.   function FindMark(markId : AllStrings;
  192.                     markoffset : Integer;
  193.                     var b : BlockType) : Boolean;
  194.     {-Find the last memory block matching idstring at offset idoffset}
  195.   var
  196.     found : Boolean;
  197.  
  198.     function HasIDstring(segment : Integer;
  199.                          idString : AllStrings;
  200.                          idOffset : Integer) : Boolean;
  201.       {-Return true if idstring is found at segment:idoffset}
  202.     var
  203.       tString : AllStrings;
  204.       len : Byte;
  205.     begin
  206.       len := Length(idString);
  207.       tString[0] := Chr(len);
  208.       Move(Mem[segment:idOffset], tString[1], len);
  209.       HasIDstring := (tString = idString);
  210.     end {HasIDstring} ;
  211.  
  212.   begin
  213.     {Scan from the last block down}
  214.     b := BlockNum;
  215.     found := False;
  216.     repeat
  217.       if Blocks[b].psp = CSeg then
  218.         {Assure this program's command line is not matched}
  219.         b := Pred(b)
  220.       else if HasIDstring(Blocks[b].psp, markId, markoffset) then
  221.         {mark found}
  222.         found := True
  223.       else
  224.         {Not a mark}
  225.         b := Pred(b);
  226.     until (b < 1) or found;
  227.     FindMark := found;
  228.   end {Findmark} ;
  229.  
  230.   function ExecutableBlock(PspHex : Integer) : Boolean;
  231.     {-Return true if psphex corresponds to an executable code block}
  232.   var
  233.     b : BlockType;
  234.   begin
  235.     for b := BlockNum downto 1 do
  236.       {Search back to find executable rather than environment block}
  237.       if Blocks[b].psp = PspHex then begin
  238.         ExecutableBlock := True;
  239.         Exit;
  240.       end;
  241.     ExecutableBlock := False;
  242.   end {ExecutableBlock} ;
  243.  
  244.   procedure InitChangeArray(WatchBlock : BlockType);
  245.     {-Initialize information regarding the WATCH data block}
  246.   var
  247.     watchindex : Integer;
  248.     p : ^ChangeBlock;
  249.   begin
  250.     {Store the segment of the WATCH data area}
  251.     WatchSeg := Blocks[WatchBlock].psp;
  252.  
  253.     {Maximum offset in WATCH data area}
  254.     ActualMax := MemW[WatchSeg:NextChange];
  255.  
  256.     {Transfer changes from WATCH into a buffer array}
  257.     watchindex := 0;
  258.     ChangeMax := 0;
  259.     while watchindex < ActualMax do begin
  260.       p := Ptr(WatchSeg, ChangeVectors+watchindex);
  261.       Move(p^, Changes[ChangeMax], SizeOf(ChangeBlock));
  262.       watchindex := watchindex+SizeOf(ChangeBlock);
  263.       if watchindex < ActualMax then
  264.         ChangeMax := Succ(ChangeMax);
  265.     end;
  266.   end {InitChangeArray} ;
  267.  
  268.   procedure PutWatch(chg : ChangeBlock; var watchindex : Integer);
  269.     {-Put a change block back into WATCH}
  270.   var
  271.     p : ^ChangeBlock;
  272.   begin
  273.     p := Ptr(WatchSeg, ChangeVectors+watchindex);
  274.     Move(chg, p^, SizeOf(ChangeBlock));
  275.     watchindex := watchindex+SizeOf(ChangeBlock);
  276.   end {PutWatch} ;
  277.  
  278.   procedure ActivateTSR(PspHex : Integer);
  279.     {-Patch out the active interrupt vectors of a specified TSR}
  280.   var
  281.     nextchg, chg, watchindex : Integer;
  282.     checking, didsomething : Boolean;
  283.   begin
  284.     didsomething := False;
  285.     watchindex := 0;
  286.     chg := 0;
  287.  
  288.     {Scan looking for the specified PSP}
  289.     while chg <= ChangeMax do begin
  290.       with Changes[chg] do
  291.         case ID of
  292.  
  293.           $FF :               {This record starts a new PSP}
  294.             begin
  295.               checking := (PspAdd = PspHex);
  296.               nextchg := Succ(chg);
  297.               if checking then
  298.                 {Turn off interrupts}
  299.                 inline($FA)
  300.               else
  301.                 {Turn on interrupts}
  302.                 inline($FB);
  303.             end;
  304.  
  305.           $01 :               {This record has an inactive vector redefinition}
  306.             if checking then begin
  307.               {We're in the proper PSP}
  308.               didsomething := True;
  309.               {Change the ID to indicate that vector is active}
  310.               ID := 0;
  311.               {Put the original vector code back in place}
  312.               nextchg := Succ(chg);
  313.               if (Changes[nextchg].ID <> 2) or (Changes[nextchg].VecNum <> VecNum) then
  314.                 Abort('Program error in Activate, patch record not found');
  315.               {Restore the patched over code}
  316.               Move(Changes[nextchg].SaveCode, Mem[VecSeg:VecOfs], 6);
  317.               {Don't output the following patch record}
  318.               nextchg := Succ(nextchg);
  319.             end else
  320.               nextchg := Succ(chg);
  321.  
  322.         else
  323.           nextchg := Succ(chg);
  324.         end;
  325.  
  326.       {Put the change block back into WATCH}
  327.       PutWatch(Changes[chg], watchindex);
  328.       {Advance to the next change record}
  329.       chg := nextchg;
  330.     end;
  331.  
  332.     {Store the count back into WATCH}
  333.     MemW[WatchSeg:NextChange] := watchindex;
  334.  
  335.     if not(didsomething) then
  336.       Abort('No changes were needed to activate '+Hex(PspHex));
  337.  
  338.   end {ActivateTSR} ;
  339.  
  340.   procedure DeactivateTSR(PspHex : Integer);
  341.     {-Patch out the active interrupt vectors of a specified TSR}
  342.   var
  343.     newchange : ChangeBlock;
  344.     chg, watchindex, curpsp : Integer;
  345.     putrec, checking, didsomething : Boolean;
  346.     name : pathname;
  347.  
  348.     procedure PutPatch(vecn : Byte; vecs, veco, curpsp : Integer);
  349.       {-Patch vector entry point with JMP to previous controlling vector}
  350.     label
  351.       90;
  352.     var
  353.       vec : ^Integer;
  354.       chg : Integer;
  355.     begin
  356.       {Get the original vector from WATCH}
  357.       Move(Mem[WatchSeg:(OrigVectors+(vecn shl 2))], vec, 4);
  358.  
  359.       {Scan the Changes array to look for redefinition of this vector}
  360.       for chg := 0 to ChangeMax do begin
  361.         with Changes[chg] do
  362.           case ID of
  363.             0, 1 :            {This is or was a redefined vector}
  364.               if vecn = VecNum then
  365.                 {It's the vector we're interested in}
  366.                 {Store the latest value of the vector}
  367.                 Move(VecOfs, vec, 4);
  368.             $FF :             {This record starts a new PSP}
  369.               if PspAdd = curpsp then
  370.                 {Stop when we get to the PSP that is being disabled}
  371.                 goto 90;
  372.           end;
  373.       end;
  374. 90:
  375.       {Patch the vector entry point into a JMP FAR vec}
  376.       Mem[vecs:veco] := $EA;
  377.       Move(vec, Mem[vecs:Succ(veco)], 4);
  378.     end {PutPatch} ;
  379.  
  380.     function CountVecs(chg : Integer) : Integer;
  381.       {-Return count of vectors taken over by the PSP starting at changeblock chg}
  382.     var
  383.       count : Integer;
  384.       ID : Byte;
  385.     begin
  386.       count := 0;
  387.       repeat
  388.         {Skip over the first one, which defines the current PSP}
  389.         chg := Succ(chg);
  390.         ID := Changes[chg].ID;
  391.         if ID = 0 then
  392.           count := Succ(count);
  393.       until ID = $FF;
  394.       CountVecs := count;
  395.     end {CountVecs} ;
  396.  
  397.   begin
  398.  
  399.     {Scan looking for the specified PSP}
  400.     didsomething := False;
  401.     watchindex := 0;
  402.  
  403.     for chg := 0 to ChangeMax do begin
  404.       putrec := True;
  405.       with Changes[chg] do
  406.         case ID of
  407.  
  408.           $FF :               {This record starts a new PSP}
  409.             begin
  410.               checking := (PspAdd = PspHex);
  411.               if checking then begin
  412.                 {Store the current PSP}
  413.                 curpsp := PspAdd;
  414.                 {Make sure WATCH has room for the extra changes}
  415.                 if watchindex+(CountVecs(chg)*SizeOf(ChangeBlock)) >
  416.                 MaxChanges*SizeOf(ChangeBlock) then
  417.                   Abort('Insufficient space in WATCH data area');
  418.                 {Turn off interrupts}
  419.                 inline($FA);
  420.               end else
  421.                 {Turn on interrupts}
  422.                 inline($FB);
  423.             end;
  424.  
  425.           $00 :               {This record has an active vector redefinition}
  426.             if checking then begin
  427.               {We're in the proper PSP}
  428.               didsomething := True;
  429.  
  430.               {Change the ID to indicate that vector is inactive}
  431.               ID := 1;
  432.               {Output the record now so that the new record can immediately follow}
  433.               PutWatch(Changes[chg], watchindex);
  434.               putrec := False;
  435.  
  436.               {Output a new change record so we can reactivate later}
  437.               {Indicate this is a patch record}
  438.               newchange.ID := 2;
  439.               {Save which vector it goes with}
  440.               newchange.VecNum := VecNum;
  441.               {Save the code we'll patch over}
  442.               Move(Mem[VecSeg:VecOfs], newchange.SaveCode, 6);
  443.               {Output the record to the WATCH area}
  444.               PutWatch(newchange, watchindex);
  445.               {Patch in a JMP to the previous vector}
  446.               PutPatch(VecNum, VecSeg, VecOfs, curpsp);
  447.             end;
  448.  
  449.         end;
  450.       if putrec then
  451.         {Put the change block back into WATCH}
  452.         PutWatch(Changes[chg], watchindex);
  453.     end;
  454.  
  455.     {Store the count back into WATCH}
  456.     MemW[WatchSeg:NextChange] := watchindex;
  457.  
  458.     if not(didsomething) then
  459.       Abort('No changes were needed to deactivate '+tsrname);
  460.  
  461.   end {DeactivateTSR} ;
  462.  
  463.   procedure GetOptions;
  464.     {-Analyze command line for options}
  465.   var
  466.     arg : AllStrings;
  467.     arglen : Byte absolute arg;
  468.     i, code : Integer;
  469.  
  470.     procedure WriteHelp;
  471.       {-Show the options}
  472.     begin
  473.       WriteLn('DISABLE ', Version, ', by TurboPower Software');
  474.       WriteLn('====================================================');
  475.  
  476.       WriteLn('DISABLE allows you to selectively disable and reenable a');
  477.       WriteLn('TSR while leaving it in memory. To run DISABLE, you must');
  478.       WriteLn('have previously installed the TSR utility WATCH.');
  479.       WriteLn;
  480.       WriteLn('DISABLE is command-line driven. You specify a single TSR by');
  481.       WriteLn('its name (if you are running DOS 3.x) or by its address as');
  482.       WriteLn('determined from a MAPMEM report. Addresses must be preceded');
  483.       WriteLn('by a dollar sign "$" and specified in hex.');
  484.       WriteLn;
  485.       WriteLn('DISABLE accepts the following command line syntax:');
  486.       WriteLn;
  487.       WriteLn('  DISABLE TSRname|$PSPaddress [Options]');
  488.       WriteLn;
  489.       WriteLn('Options may be preceded by either / or -. Valid options');
  490.       WriteLn('are as follows:');
  491.       WriteLn;
  492.       WriteLn('     /A     reActivate the specified TSR.');
  493.       WriteLn('     /?     Write this help screen.');
  494.       Halt(1);
  495.     end {WriteHelp} ;
  496.  
  497.     function DOSversion : Byte;
  498.       {-return the major version number of DOS}
  499.     var
  500.       reg : Registers;
  501.     begin
  502.       reg.ah := $30;
  503.       MsDos(reg);
  504.       DOSversion := reg.al;
  505.     end {dosversion} ;
  506.  
  507.     function Owner(envseg : Integer) : Pathname;
  508.       {-return the name of the owner program of an MCB}
  509.     type
  510.       chararray = array[0..32767] of Char;
  511.     var
  512.       e : ^chararray;
  513.       i : Integer;
  514.       t : Pathname;
  515.  
  516.       function LongPos(m : Pathname; var s : chararray) : Integer;
  517.         {-return the position number of m in s, or 0 if not found}
  518.       var
  519.         mlen : Byte absolute m;
  520.         mc : Char;
  521.         ss : Pathname;
  522.         i, maxindex : Integer;
  523.         found : Boolean;
  524.       begin
  525.         i := 0;
  526.         maxindex := SizeOf(s)-mlen;
  527.         ss[0] := m[0];
  528.         if mlen > 0 then begin
  529.           mc := m[1];
  530.           repeat
  531.             while (s[i] <> mc) and (i <= maxindex) do
  532.               i := Succ(i);
  533.             if s[i] = mc then begin
  534.               Move(s[i], ss[1], Length(m));
  535.               found := (ss = m);
  536.               if not(found) then
  537.                 i := Succ(i);
  538.             end;
  539.           until found or (i > maxindex);
  540.           if not(found) then
  541.             i := 0;
  542.         end;
  543.         LongPos := i;
  544.       end {longpos} ;
  545.  
  546.       procedure StripNonAscii(var t : Pathname);
  547.         {-return an empty string if t contains any non-printable characters}
  548.       var
  549.         ipos : Byte;
  550.         goodname : Boolean;
  551.       begin
  552.         goodname := True;
  553.         for ipos := 1 to Length(t) do
  554.           if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  555.             goodname := False;
  556.         if not(goodname) then
  557.           t := '';
  558.       end {stripnonascii} ;
  559.  
  560.       procedure StripPathname(var pname : Pathname);
  561.         {-remove leading drive or path name from the input}
  562.       var
  563.         spos, cpos, rpos : Byte;
  564.       begin
  565.         spos := Pos('\', pname);
  566.         cpos := Pos(':', pname);
  567.         if spos+cpos = 0 then
  568.           Exit;
  569.         if spos <> 0 then begin
  570.           {find the last slash in the pathname}
  571.           rpos := Length(pname);
  572.           while (rpos > 0) and (pname[rpos] <> '\') do
  573.             rpos := Pred(rpos);
  574.         end else
  575.           rpos := cpos;
  576.         Delete(pname, 1, rpos);
  577.       end {strippathname} ;
  578.  
  579.       procedure StripExtension(var pname : Pathname);
  580.         {-remove the file extension}
  581.       var
  582.         dotpos : Byte;
  583.       begin
  584.         dotpos := Pos('.', pname);
  585.         if dotpos <> 0 then
  586.           Delete(pname, dotpos, 64);
  587.       end {stripextension} ;
  588.  
  589.     begin
  590.       {point to the environment string}
  591.       e := Ptr(envseg, 0);
  592.  
  593.       {find end of the standard environment}
  594.       i := LongPos(#0#0, e^);
  595.       if i = 0 then begin
  596.         {something's wrong, exit gracefully}
  597.         Owner := '';
  598.         Exit;
  599.       end;
  600.  
  601.       {end of environment found, get the program name that follows it}
  602.       t := '';
  603.       i := i+4;               {skip over #0#0#args}
  604.       repeat
  605.         t := t+e^[i];
  606.         i := Succ(i);
  607.       until (Length(t) > 64) or (e^[i] = #0);
  608.  
  609.       StripNonAscii(t);
  610.       if t = '' then
  611.         Owner := 'N/A'
  612.       else begin
  613.         StripPathname(t);
  614.         StripExtension(t);
  615.         if t = '' then t := 'N/A';
  616.         Owner := StUpcase(t);
  617.       end;
  618.  
  619.     end {owner} ;
  620.  
  621.     function FindOwner(name : AllStrings) : Integer;
  622.       {-Return segment of executable block with specified name}
  623.     var
  624.       b : BlockType;
  625.     begin
  626.       name := StUpcase(name);
  627.       {Scan the blocks in reverse order}
  628.       for b := BlockNum downto 1 do
  629.         with Blocks[b] do
  630.           if Succ(mcb) = psp then
  631.             {This block is an executable block}
  632.             if Owner(MemW[psp:$2C]) = name then begin
  633.               {Found it}
  634.               FindOwner := psp;
  635.               Exit;
  636.             end;
  637.       Abort('Cannot find TSR with name '+name);
  638.     end {FindOwner} ;
  639.  
  640.   begin
  641.  
  642.     WriteLn;
  643.  
  644.     {Initialize defaults}
  645.     PspHex := 0;
  646.     Activate := False;
  647.  
  648.     i := 1;
  649.     while i <= ParamCount do begin
  650.       arg := ParamStr(i);
  651.       if (arg[1] = '?') then
  652.         WriteHelp
  653.       else if (arg[1] = '-') or (arg[1] = '/') then
  654.         case arglen of
  655.           1 : Abort('Missing command option following '+arg);
  656.           2 : case UpCase(arg[2]) of
  657.                 '?' : WriteHelp;
  658.                 'A' : Activate := True;
  659.               else
  660.                 Abort('Unknown command option: '+arg);
  661.               end;
  662.         else
  663.           Abort('Unknown command option: '+arg);
  664.         end
  665.       else begin
  666.         {TSR to change}
  667.         if arg[1] = '$' then begin
  668.           {Treat as hex address}
  669.           Val(arg, PspHex, code);
  670.           if code <> 0 then
  671.             Abort('Invalid hex address specification: '+arg);
  672.         end else if DOSversion >= 3 then
  673.           {Treat as PSP owner name - scan to find proper PSP}
  674.           PspHex := FindOwner(arg)
  675.         else
  676.           Abort('Must have DOS 3.x to find TSRs by name');
  677.         TsrName := StUpcase(arg);
  678.       end;
  679.       i := Succ(i);
  680.     end;
  681.     if PspHex = 0 then
  682.       abort('No TSR name or address specified');
  683.  
  684.   end {GetOptions} ;
  685.  
  686. begin
  687.  
  688.   {Get all allocated memory blocks in normal memory}
  689.   {Must do first to support TSRs by name in GetOptions}
  690.   FindTheBlocks;
  691.  
  692.   {Analyze command line for options}
  693.   GetOptions;
  694.  
  695.   {Find the watch block}
  696.   if not(FindMark(WatchID, WatchOffset, WatchBlock)) then
  697.     Abort('WATCH must be installed in order to use DISABLE');
  698.  
  699.   {Assure PspHex corresponds to an executable block}
  700.   if not(ExecutableBlock(PspHex)) then
  701.     Abort('Address specified does not correspond to a TSR');
  702.  
  703.   {Initialize information regarding the WATCH data block}
  704.   InitChangeArray(WatchBlock);
  705.  
  706.   {Activate or deactivate the TSR}
  707.   if Activate then
  708.     ActivateTSR(PspHex)
  709.   else
  710.     DeactivateTSR(PspHex);
  711.  
  712.   {Write success message}
  713.   Write('DISABLE ', Version, ' ');
  714.   if not(Activate) then
  715.     Write('de');
  716.   Write('activated ');
  717.   if TsrName[1] = '$' then
  718.     Write('TSR at ');
  719.   WriteLn(TsrName);
  720.  
  721. end.
  722.